﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XZen.Cdn.Client.Blobs;
using System.Net;
using System.Diagnostics;
using System.Runtime.Caching;
using XZen.Cdn.Client;
using XZen.Blogger.Execptions;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;

namespace XZen.Blogger
{
    public interface IMagazineService
    {
        List<Magazine> GetMagazines();
        Magazine GetMagazineByName(string magName, bool useCache = false);
        Magazine GetMagazine(long? blogEntryId, bool useCache = false);
        long? CreateMagazine(Magazine Magazine);
        Magazine EditMagazine(Magazine Magazine);
        long? DeleteMagazine(Magazine Magazine);

        List<Issue> GetIssues(long? magId);
        Issue GetIssue(long? issueId);
        long? CreateIssue(long? magId, Issue issue);
        Issue EditIssue(Issue issue);
        long? DeleteIssue(Issue issue);

        List<Chapter> GetChapters(long? issueId);
        Chapter GetChapter(long? chapter);
        long? CreateChapter(long? issueId, Chapter chapter);
        Chapter EditChapter(Chapter chapter);
        long? DeleteChapter(Chapter chapter);

        List<Article> GetArticles(long? chapterId);
        Article GetArticle(long? artId);
        long? CreateArticle(long? chapterId, Article art);
        Article EditArticle(Article art);
        Article PublishArticle(Article art);
        long? DeleteArticle(Article art);
    }

    [Serializable]
    public class MagazineService : IMagazineService
    {
        #region Private Variables & Constructor
        private long? userId;
        private long? subscriptionId;
        private long? rootFolderId;
        private long? magsFolderId;
        private long? imageFolderId;
        private string user;
        private string subcription;
        private string baseUri;
        private ContentWriter writer;
        MemoryCache _cacheStore = new MemoryCache("MAG-MAP");
        private int cacheDurationInMinutes = 10;
        private const string MAGS_FOLDER_NAME = "Magazines";
        private const string ISSUES_FOLDER_NAME = "Issues";
        private const string CHAPTERS_FOLDER_NAME = "Chapters";
        private const string IMAGES_FOLDER_NAME = "Magazines";

        private const string COMMENTS_FOLDER_NAME = "Comments";
        private const string RATING_FOLDER_NAME = "Rating";
        private const string POSTING_DOCUMENT_NAME = "index.htm";
        private const string PROPERTY_BAG = "Properties";
        BlobServiceClient Contents;
        public MemoryCache Cache
        {
            get { return _cacheStore; }
            set { _cacheStore = value; }
        }
        public static MagazineService _loggedInService;
        public static string _serviceLock = "cool";

        public MagazineService(string userName, string subscriptionName)
        {
            user = userName;
            subcription = subscriptionName;
        }
        #endregion

        #region Book Keeping
        public static MagazineService Instance
        {
            get
            {
                if (_loggedInService == null)
                    throw new LoginException("Not yet initialized, must be initialized and logged in");
                return _loggedInService;
            }
        }

        public static MagazineService Create(string userName, string password, string contentUri, string subscriptionName)
        {
            _loggedInService = new MagazineService(userName, subscriptionName);
            _loggedInService.Login(userName, password, contentUri);
            return _loggedInService;
        }
        public string Login(string userName, string password, string contentUri)
        {
            if (_loggedInService != null)
            {
                Debug.WriteLine("Warning...Already initialized");
            }
            baseUri = contentUri;
            writer = new ContentWriter(contentUri, new ContentSetting(userName, password, contentUri));
            Contents = writer.Client;
            userId = Contents.GetUserByName(user);
            subscriptionId = Contents.GetSubscriptrionByName(userId, subcription);
            rootFolderId = Contents.GetStorageRoot(subscriptionId);
            lock (_serviceLock)
            {
                _loggedInService = this;
                writer.AppSettings.IsInitialized = true;
            }
            return writer.Token;
        }
        #endregion

        #region Private Liked Function thus internal
        internal Folder GetMagazinesRoot()
        {
            return GetCachedSubFolder(rootFolderId, MAGS_FOLDER_NAME);
        }
        internal Folder GetCachedSubFolder(long? folderId, string folderName)
        {
            string key = FolderIDNameKey(folderId, folderName);
            var item = Cache.GetCacheItem(key);
            Folder folder = null;
            if (item == null)
            {
                folder = Contents.FindSubFolder(folderId, folderName);
                if (folder != null)
                    Cache.Set(key, folder, DefaultPolicy());
            }
            else
            {
                folder = (Folder)item.Value;
            }
            return folder;
        }
        internal void ClearCache(long? Id)
        {
            string key = FolderIDKey(Id);
            Cache.Remove(key);
        }
        internal void ClearCache(Folder folder)
        {
            ClearCache(folder.FolderID);
        }
        internal void ClearCache(File file)
        {
            ClearCache(file.FileID);
        }
        internal Folder GetCachedFolder(long? folderId)
        {
            string key = FolderIDKey(folderId);
            var item = Cache.GetCacheItem(key);
            Folder folder = null;
            if (item == null)
            {
                folder = Contents.GetFolder(folderId);
                if (folder != null)
                    Cache.Set(key, folder, DefaultPolicy());
            }
            else
            {
                folder = (Folder)item.Value;
            }
            return folder;
        }

        /*******************  LOW LEVEL  ************************/
        internal string FolderIDKey(long? folderId)
        {
            return "FolderByID=" + folderId.ToString();
        }
        internal string FolderIDNameKey(long? folderId, string folderName)
        {
            return "FolderByID=" + folderId.ToString() + "&FolderByName=" + folderName;
        }
        internal CacheItemPolicy DefaultPolicy()
        {
            CacheItemPolicy policy = new CacheItemPolicy() { AbsoluteExpiration = DateTime.Now.AddMinutes(cacheDurationInMinutes) };
            return policy;
        }
        public void SaveDataProperties(Attributer ob)
        {
            long? folderId = ob.ID.ToNullableLong();
            ob.User = user;
            ob.Source = subcription;
            ob.Uri = baseUri;
            ob.ClassName = ob.ToString();
            ob.UpdatedOn = DateTime.Now.ToString();

            var attrs = ob.ToAttributes();
            Contents.AddFolderAttributes(folderId, attrs);
        }
        public long? GetImageFolderID(long? folderId)
        {
            Folder entryFolder = GetCachedSubFolder(folderId, IMAGES_FOLDER_NAME);
            if (entryFolder == null)
            {
                long? sfid = Contents.CreateSubFolder(folderId, IMAGES_FOLDER_NAME );
                return sfid;
            }
            return entryFolder.FolderID;
        }
        public string UniqueID()
        {
            Random rand = new Random((int)DateTime.Now.Ticks);
            var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
            //Make sure it StartsWith Capital
            var text = new StringBuilder(("" + possible[rand.Next(25)]));
            //Build the string
            for (var i = 0; i < 12; i++)
            {
                int pos = rand.Next(60);
                text.Append(possible[pos]);
            }
            return text.ToString();
        }

        public string GetFileUrl(long? folderId, string magName, byte[] imageData, string fileName)
        {
            var imageFolderId = GetImageFolderID(folderId);

            Image returnImage = ResizeImage(300,400, imageData);
            byte [] smallImage = ToBuffer(returnImage, ImageFormat.Jpeg);

            fileName = UniqueID() + ".jpg";

            long? fileId = 0;
            var file = Contents.GetFileByName(imageFolderId, fileName);
            if (file == null)
            {
                fileId = Contents.CreateFileWithContent(imageFolderId, fileName, smallImage, "image/jpg");
                file = Contents.GetFile(fileId);
            }
            else
            {
                fileId = file.FileID;
                Contents.UploadFileContent(fileId, smallImage, "image/jpg");
            }
            return ToUrl(file);
        }

        byte[] ToBuffer(Image image, ImageFormat format)
        {
            var ms = new System.IO.MemoryStream();
            image.Save(ms, format);
            byte[] data = ms.GetBuffer();
            ms.Dispose();
            return data;
        }
        public string ToUrl(File file)
        {
            //native...

            string url = string.Format("{0}/Content/{1}/{2}{3}", baseUri, user, subcription, file.Uri);
            return url;
        }
        public Image ResizeImage(int newWidth, int newHeight, byte[] imageData)
        {
            var ms = new System.IO.MemoryStream(imageData);
            Image imgPhoto = Image.FromStream(ms);

            int sourceWidth = imgPhoto.Width;
            int sourceHeight = imgPhoto.Height;

            //Consider vertical pics
            if (sourceWidth < sourceHeight)
            {
                int buff = newWidth;

                newWidth = newHeight;
                newHeight = buff;
            }

            int sourceX = 0, sourceY = 0, destX = 0, destY = 0;
            float nPercent = 0, nPercentW = 0, nPercentH = 0;

            nPercentW = ((float)newWidth / (float)sourceWidth);
            nPercentH = ((float)newHeight / (float)sourceHeight);
            if (nPercentH < nPercentW)
            {
                nPercent = nPercentH;
                destX = System.Convert.ToInt16((newWidth -
                          (sourceWidth * nPercent)) / 2);
            }
            else
            {
                nPercent = nPercentW;
                destY = System.Convert.ToInt16((newHeight -
                          (sourceHeight * nPercent)) / 2);
            }

            int destWidth = (int)(sourceWidth * nPercent);
            int destHeight = (int)(sourceHeight * nPercent);


            Bitmap bmPhoto = new Bitmap(newWidth, newHeight);

            bmPhoto.SetResolution(imgPhoto.HorizontalResolution, imgPhoto.VerticalResolution);

            Graphics grPhoto = Graphics.FromImage(bmPhoto);
            grPhoto.Clear(Color.White);
            grPhoto.InterpolationMode = InterpolationMode.Low;

            grPhoto.DrawImage(imgPhoto,
                new Rectangle(destX, destY, destWidth, destHeight),
                new Rectangle(sourceX, sourceY, sourceWidth, sourceHeight),
                GraphicsUnit.Pixel);

            int quality = 50;
            // Encoder parameter for image quality 
            EncoderParameter qualityParam =
                new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
            // Jpeg image codec 
            ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg");

            EncoderParameters encoderParams = new EncoderParameters(1);
            encoderParams.Param[0] = qualityParam;

            ms.Dispose();
            ms = new System.IO.MemoryStream();
            bmPhoto.Save(ms, jpegCodec, encoderParams);

            imgPhoto.Dispose();
            imgPhoto = Image.FromStream(ms);

            grPhoto.Dispose();
            bmPhoto.Dispose();
            return imgPhoto;
        }

        /// <summary> 
        /// Returns the image codec with the given mime type 
        /// </summary> 
        private static ImageCodecInfo GetEncoderInfo(string mimeType)
        {
            // Get image codecs for all image formats 
            ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();

            // Find the correct image codec 
            for (int i = 0; i < codecs.Length; i++)
                if (codecs[i].MimeType == mimeType)
                    return codecs[i];
            return null;
        }
        #endregion

        #region All List
        public List<Magazine> GetMagazines()
        {
            Folder blogFolder = GetMagazinesRoot();
            if (blogFolder == null)
            {
                //He has not created a Magazine so far
                return new List<Magazine>();
            }
            var subfolder = Contents.ListFolders(blogFolder.FolderID);
            var blogs = (from f in subfolder select ToMagazine(f)).ToList();
            return blogs;
        }
        public List<Chapter> GetChapters(long? issueId)
        {
            var subfolder = Contents.ListFolders(issueId);
            var chapters = (from f in subfolder select ToChapter(f)).ToList();
            return chapters;
        }
        public List<Issue> GetIssues(long? magId)
        {
            //these are arts
            var subfolder = Contents.ListFolders(magId);
            var issues = (from f in subfolder select ToIssue(f)).ToList();
            return issues;
        }
        public List<Article> GetArticles(long? blogId)
        {
            //these are arts
            var subfolder = Contents.ListFolders(blogId);
            var posts = (from f in subfolder select ToArticle(f)).ToList();
            return posts;
        }
        #endregion

        #region All Gets
        public Magazine GetMagazineByName(string blogName, bool useCache = false)
        {
            Folder blogFolder = GetMagazinesRoot();
            //space terminated string
            var folder = (useCache) ? GetCachedSubFolder(blogFolder.FolderID, blogName)
                : Contents.FindSubFolder(blogFolder.FolderID, blogName);
            var mag = ToMagazine(folder);
            return mag;
        }
        public Magazine GetMagazine(long? magId, bool useCache = false)
        {
            var folder = (useCache) ? GetCachedFolder(magId) : Contents.GetFolder(magId);
            var mag = ToMagazine(folder);
            return mag;
        }
        public Article GetArticle(long? artEntryId)
        {
            var folder = GetCachedFolder(artEntryId);
            File file = Contents.GetFileByName(folder.FolderID, POSTING_DOCUMENT_NAME);
            var bytes = Contents.GetFileContent(file.FileID);
            var art = ToArticle(folder);
            art.Body = UTF8Encoding.UTF8.GetString(bytes);
            return art;
        }
        public Issue GetIssue(long? issueId)
        {
            var folder = GetCachedFolder(issueId);
            var ob = ToIssue(folder);
            return ob;
        }
        public Chapter GetChapter(long? chapterId)
        {
            var folder = GetCachedFolder(chapterId);
            var ob = ToChapter(folder);
            return ob;
        }
        #endregion

        #region Create
        public long? CreateMagazine(Magazine mag)
        {
            //Set it First
            mag.User = user;
            mag.CreatedOn = DateTime.Now.ToString();

            // if there is already [Root=>Magazines] folder..
            var magFolder = GetMagazinesRoot();

            if (magFolder == null)
            {
                magsFolderId = Contents.CreateSubFolder(rootFolderId, MAGS_FOLDER_NAME);
            }
            else
            {
                magsFolderId = magFolder.FolderID;
            }

            string magname = mag.Name.Replace(' ', '_');
            var magEntryId = Contents.CreateSubFolder(magsFolderId, magname);
            imageFolderId = Contents.CreateSubFolder(magEntryId, IMAGES_FOLDER_NAME);

            var folder = GetCachedFolder(magEntryId);
            mag.ID = magEntryId.ToString();
            SaveDataProperties(mag);
            ClearCache(magEntryId);
            return magEntryId;
        }
        public long? CreateArticle(long? blogEntryId, Article art)
        {
            art.User = user;
            string postName = art.Name.Replace(" ", "_");
            long? artFolderId;
            //Get the Article Folder
            Folder artFolder = GetCachedSubFolder(blogEntryId, postName);

            if (artFolder == null)
            {
                artFolderId = Contents.CreateSubFolder(blogEntryId, postName);
                artFolder = GetCachedFolder(artFolderId);
            }
            else
            {
                artFolderId = artFolder.FolderID;
            }

            //Create Comments folder
            long? commentsFolderId = Contents.CreateSubFolder(artFolderId, COMMENTS_FOLDER_NAME);
            long? ratingFolderId = Contents.CreateSubFolder(artFolderId, RATING_FOLDER_NAME);
            var fileId = Contents.CreateFileWithContent(artFolderId, POSTING_DOCUMENT_NAME, UTF8Encoding.UTF8.GetBytes(art.Body), "text/html");

            var commentfileId = Contents.CreateFileWithContent(artFolderId, PROPERTY_BAG, UTF8Encoding.UTF8.GetBytes("Properties"), "text/html");

            art.ID = artFolderId.ToString();
            SaveDataProperties(art);
            ClearCache(artFolderId);
            return artFolderId;
        }
        public long? CreateIssue(long? magId, Issue issue)
        {
            issue.User = user;
            string postName = issue.Name.Replace(" ", "_");
            long? issueFolderId;
            Folder artFolder = GetCachedSubFolder(magId, postName);

            if (artFolder == null)
            {
                issueFolderId = Contents.CreateSubFolder(magId, postName);
                artFolder = GetCachedFolder(issueFolderId);
            }
            else
            {
                issueFolderId = artFolder.FolderID;
            }
            issue.ID = issueFolderId.ToString();
            SaveDataProperties(issue);
            ClearCache(issueFolderId);
            return issueFolderId;
        }
        public long? CreateChapter(long? issueId, Chapter chapter)
        {
            chapter.User = user;
            string postName = chapter.Name.Replace(" ", "_");
            long? chapterFolderId;
            Folder artFolder = GetCachedSubFolder(issueId, postName);

            if (artFolder == null)
            {
                chapterFolderId = Contents.CreateSubFolder(issueId, postName);
                artFolder = GetCachedFolder(chapterFolderId);
            }
            else
            {
                chapterFolderId = artFolder.FolderID;
            }
            chapter.ID = chapterFolderId.ToString();
            SaveDataProperties(chapter);
            ClearCache(chapterFolderId);
            return chapterFolderId;
        }
        #endregion

        #region Edit
        public Magazine EditMagazine(Magazine mag)
        {
            //Set it First
            mag.User = user;
            Folder entryFolder = GetCachedFolder(mag.ToID());
            entryFolder.FolderName = mag.Name;
            entryFolder.Caption = mag.Title;
            entryFolder.Status = (int)FileStatus.Visited;
            entryFolder = Contents.UpdateFolder(entryFolder);

            SaveDataProperties(mag);
            ClearCache(entryFolder.FolderID);

            return ToMagazine(entryFolder);
        }

        public Issue EditIssue(Issue issue)
        {
            issue.User = user;
            Folder entryFolder = GetCachedFolder(issue.ToID());
            entryFolder.FolderName = issue.Name;
            entryFolder.Caption = issue.Title;
            entryFolder.Status = (int)FileStatus.Visited;
            entryFolder = Contents.UpdateFolder(entryFolder);
            SaveDataProperties(issue);
            ClearCache(entryFolder.FolderID);
            return ToIssue(entryFolder);
        }




        public Chapter EditChapter(Chapter chapter)
        {
            chapter.User = user;
            Folder entryFolder = GetCachedFolder(chapter.ToID());
            entryFolder.FolderName = chapter.Name;
            entryFolder.Caption = chapter.Title;
            entryFolder.Status = (int)FileStatus.Visited;
            entryFolder = Contents.UpdateFolder(entryFolder);
            SaveDataProperties(chapter);
            ClearCache(entryFolder.FolderID);
            return ToChapter(entryFolder);
        }



        public Article EditArticle(Article art)
        {
            art.User = user;
            Folder artFolder = GetCachedFolder(art.ToID());
            if (artFolder == null)
            {
                throw new ArgumentException("Post already exists");
            }
            File file = Contents.GetFileByName(artFolder.FolderID, POSTING_DOCUMENT_NAME);
            long? fileId = file.FileID;
            artFolder.FolderName = art.Name;
            artFolder.Caption = art.Title;
            artFolder.Status = (int)FileStatus.Draft;
            Contents.UpdateFolder(artFolder);
            SaveDataProperties(art);
            // this will have newer version...
            Contents.UploadFileContent(fileId, UTF8Encoding.UTF8.GetBytes(art.Body), "text/html");
            ClearCache(artFolder.FolderID);
            return ToArticle(artFolder);
        }
        public Article PublishArticle(Article art)
        {
            Folder artFolder = GetCachedFolder(art.ToID());
            if (artFolder == null)
            {
                throw new ArgumentException("Post already exists");
            }
            File file = Contents.GetFileByName(artFolder.FolderID, "index.htm");
            long? fileId = file.FileID;
            Contents.SetFolderStatus(artFolder.FolderID, FileStatus.Posted);
            ClearCache(artFolder.FolderID);
            return ToArticle(artFolder);
        }
        #endregion

        #region Delete
        public long? DeleteMagazine(Magazine mag)
        {
            Folder entryFolder = GetCachedFolder(mag.ToID());
            ClearCache(entryFolder.FolderID);
            Contents.RemoveFolder(entryFolder.FolderID, true);
            return entryFolder.FolderID;
        }
        public long? DeleteArticle(Article art)
        {
            Folder artFolder = GetCachedFolder(art.ToID());
            ClearCache(artFolder.FolderID);
            Contents.RemoveFolder(artFolder.FolderID, true);
            return artFolder.FolderID;
        }
        public long? DeleteChapter(Chapter chapter)
        {
            Folder folder = GetCachedFolder(chapter.ToID());
            ClearCache(folder.FolderID);
            Contents.RemoveFolder(folder.FolderID, true);
            return folder.FolderID;
        }
        public long? DeleteIssue(Issue issue)
        {
            Folder folder = GetCachedFolder(issue.ToID());
            ClearCache(folder.FolderID);
            Contents.RemoveFolder(folder.FolderID, true);
            return folder.FolderID;
        }
        #endregion

        #region Apply
        public Chapter ToChapter(Folder folder)
        {
            var post = new Chapter
            {
                ID = folder.FolderID.ToString(),
                Body = folder.FolderName,
                Category = "Dont Know",
                IP = "dont know",
                Name = folder.FolderName,
                Title = folder.Caption,
                User = folder.OwnerUserName,
                IssueID = folder.ParentID.ToString()
            };
            var attrs = folder.Attributes();
            post.FillAttributes(attrs);
            return post;
        }
        public Issue ToIssue(Folder folder)
        {
            var post = new Issue
            {
                ID = folder.FolderID.ToString(),
                Body = folder.FolderName,
                Category = "Dont Know",
                IP = "dont know",
                Name = folder.FolderName,
                Title = folder.Caption,
                User = folder.OwnerUserName,
                MagazineID = folder.ParentID.ToString()
            };
            var attrs = folder.Attributes();
            post.FillAttributes(attrs);
            post.CoverPageUrl = attrs.GetString("cover");
            return post;
        }
        public Article ToArticle(Folder folder)
        {
            var post = new Article
            {
                ID = folder.FolderID.ToString(),
                Body = folder.FolderName,
                Category = "Dont Know",
                IP = "dont know",
                Name = folder.FolderName,
                Title = folder.Caption,
                User = folder.OwnerUserName,
                Status = folder.Status.ToStatus(),
                ChapterID = folder.ParentID.ToString(),
                Comments = new List<Comment>(),
                Ratings = new List<Rating>()
            };
            var attrs = folder.Attributes();
            post.FillAttributes(attrs);

            return post;
        }
        public Magazine ToMagazine(Folder folder)
        {
            //var attrs = folder.Html;
            var mag = new Magazine
            {
                ID = folder.FolderID.ToString(),
                Body = folder.FolderName,
                Category = "Dont Know",
                IP = "dont know",
                Name = folder.FolderName,
                Title = folder.Caption,
                User = folder.OwnerUserName
            };
            var attrs = folder.Attributes();
            mag.FillAttributes(attrs);
            mag.CoverPageUrl = attrs.GetString("cover");
            return mag;
        }
        public Folder Apply(Folder folder, Magazine mag)
        {
            folder.Caption = mag.Title;
            folder.FolderName = mag.Name;
            return folder;
        }
        public Folder Apply(Folder folder, Article art)
        {
            folder.Caption = art.Title;
            folder.FolderName = art.Name;
            return folder;
        }
        #endregion

    }
}
